﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PIF1006_TP1
{
    public class Matrix
    {
        #region Attributs

        double[,] tableau;

        #endregion

        public double this[int indexY, int indexX]
        {
            get
            {
                if (indexY >= 0 && indexX >= 0 && indexY < tableau.GetLength(0) && indexX < tableau.GetLength(1))
                    return tableau[indexY, indexX];
                else
                    throw new IndexOutOfRangeException("Index en dehors des limites de la matrice");
            }
            set
            {
                if (indexY >= 0 && indexX >= 0 && indexY < tableau.GetLength(0) && indexX < tableau.GetLength(1))
                    tableau[indexY, indexX] = value;
                else
                    throw new IndexOutOfRangeException("Index en dehors des limites de la matrice");
            }
        }

        public static Matrix CreateMatrixFromString(String strMatrix)
        {
            double value;
            string[] lines = strMatrix.Split('\n');

            Matrix matrix = new Matrix(lines.Length, lines[0].Split(' ').Length);
            //On parcours chacune des lignes dans la chaine de caractère
            for (int ctrLine = 0; ctrLine < lines.Length; ctrLine++)
            {
                string[] cols = lines[ctrLine].Split(' ');
                if (cols.Length != matrix.SizeX)
                    throw new Exception("Toutes les lignes doivent contenir le même nombre de colonnes et toutes les colonnes doivent contenir le même nombre de lignes");
                //on parcours tout les éléments de la ligne séparés par des espaces
                for (int ctrCol = 0; ctrCol < cols.Length; ctrCol++)
                {
                    bool isDouble = double.TryParse(cols[ctrCol], out value);
                    matrix[ctrLine, ctrCol] = isDouble ? value : 0;
                }
            }

            return matrix;
        }

        #region Contructors

        public Matrix(int sizeY, int sizeX)
        {
            if (sizeY > 0 && sizeX > 0)
                tableau = new double[sizeY, sizeX];
            else
                throw new ArgumentOutOfRangeException("La taille de la matrice doit être minimallement 1x1");
        }

        #endregion

        #region Properties

        public int SizeY
        {
            get
            {
                return tableau.GetLength(0);
            }
        }

        public int SizeX
        {
            get
            {
                return tableau.GetLength(1);
            }
        }

        public double Trace
        {
            get
            {
                double trace = 0;
                if (IsSquare)
                    for (int ctr = 0; ctr < SizeY; ctr++)
                        trace += this[ctr, ctr];
                return trace;
            }
        }

        public Matrix TransposedMatrix
        {
            get
            {
                Matrix transposedMatrix = new Matrix(this.SizeX, this.SizeY);

                for (int ctrY = 0; ctrY < this.SizeY; ctrY++)
                    for (int ctrX = 0; ctrX < this.SizeX; ctrX++)
                        transposedMatrix[ctrX, ctrY] = this[ctrY, ctrX];

                return transposedMatrix;
            }
        }

        public Matrix AdjugateMatrix
        {
            get
            {
                Matrix adjugateMatrix;
                if (IsSquare)
                {
                    adjugateMatrix = new Matrix(this.SizeY, this.SizeX);

                    for (int ctrLine = 0; ctrLine < this.SizeY; ctrLine++)
                    {
                        for (int ctrCol = 0; ctrCol < this.SizeX; ctrCol++)
                        {
                            adjugateMatrix[ctrLine, ctrCol] = Math.Pow(-1, ctrLine + ctrCol) * this.SubMatrix(ctrLine, ctrCol).Determinant();
                        }
                    }
                }
                else
                    throw new NotSquareMatrixException("La matrice n'est pas carrée");

                return adjugateMatrix;
            }
        }

        public Matrix InverseMatrix
        {
            get
            {
                Matrix inverseMatrix;
                if (IsRegular)
                {
                    inverseMatrix = this.AdjugateMatrix.TransposedMatrix.ScalarProduct(1 / this.Determinant());
                }
                else
                    throw new NotSquareMatrixException("La matrice n'est pas régulière");
                return inverseMatrix;
            }
        }

        public bool IsSquare
        {
            get
            {
                return (SizeX == SizeY);
            }
        }

        public bool IsRegular
        {
            get
            {
                return (Determinant() != 0);
            }
        }

        #endregion

        #region Methods

        public bool GetIsTriangular(TriangularType triangularType, bool strict)
        {
            bool isTriangular = true;

            if (IsSquare)
            {
                //vérification pour triangle inférieur et dual
                if (triangularType == TriangularType.InferiorTriangular || triangularType == TriangularType.DualTriangular)
                    isTriangular = GetIsTriangularInferior(strict);

                //vérification pour le triangle supérieur et dual
                if (isTriangular && (triangularType == TriangularType.SuperiorTriangular || triangularType == TriangularType.DualTriangular))
                    isTriangular = GetIsTriangularSuperior(strict);
            }
            else
                isTriangular = false;
            return isTriangular;
        }

        private bool GetIsTriangularInferior(bool strict)
        {
            bool isTriangularInferior = true;
            int ajustmentForStrict = strict ? 0 : 1;

            //On parcours le haut du triangle
            for (int ctrY = 0; ctrY < this.SizeY; ctrY++)
            {
                for (int ctrX = ctrY + ajustmentForStrict; ctrX < this.SizeX; ctrX++)
                    if (this[ctrY, ctrX] != 0)
                    {
                        isTriangularInferior = false;
                        break;
                    }
            }

            return isTriangularInferior;
        }

        private bool GetIsTriangularSuperior(bool strict)
        {
            bool isTriangularSuperior = true;
            int ajustmentForStrict = strict ? 0 : 1;

            //On parcours le bas du triangle
            for (int ctrX = 0; ctrX < this.SizeX; ctrX++)
            {
                for (int ctrY = ctrX + ajustmentForStrict; ctrY < this.SizeY; ctrY++)
                    if (this[ctrY, ctrX] != 0)
                    {
                        isTriangularSuperior = false;
                        break;
                    }
            }

            return isTriangularSuperior;
        }

        #region Addition de deux matrices

        public Matrix AddMatrice(Matrix otherMatrice)
        {
            if (!this.DimensionEquals(otherMatrice))
                throw new NotSameDimensionException("La taille des deux matrices à additionner n'est pas identique.");

            Matrix addedMatrice = new Matrix(this.SizeY, this.SizeX);

            //on additionne la valeur des éléments correspondants
            for (int ctrY = 0; ctrY < addedMatrice.SizeY; ctrY++)
                for (int ctrX = 0; ctrX < addedMatrice.SizeX; ctrX++)
                {
                    addedMatrice[ctrY, ctrX] = this[ctrY, ctrX] + otherMatrice[ctrY, ctrX];
                }

            return addedMatrice;
        }

        public static Matrix operator +(Matrix m1, Matrix m2)
        {
            return m1.AddMatrice(m2);
        }

        #endregion

        #region Multiplication d'une matrice avec un scalaire

        public Matrix ScalarProduct(double scalar)
        {
            Matrix productMatrice = new Matrix(this.SizeY, this.SizeX);
            //multiplication de chaque élément par le scalaire
            for (int ctrY = 0; ctrY < productMatrice.SizeY; ctrY++)
                for (int ctrX = 0; ctrX < productMatrice.SizeX; ctrX++)
                {
                    productMatrice[ctrY, ctrX] = this[ctrY, ctrX] * scalar;
                }

            return productMatrice;
        }

        public static Matrix operator *(double scalar, Matrix m1)
        {
            return m1.ScalarProduct(scalar);
        }

        public static Matrix operator *(Matrix m1, double scalar)
        {
            return m1.ScalarProduct(scalar);
        }

        #endregion

        #region Multiplication d'une matrice avec une autre matrice

        public Matrix MatrixProduct(Matrix otherMatrix)
        {
            double total;
            Matrix productMatrix;
            if (this.SizeX == otherMatrix.SizeY)
            {
                productMatrix = new Matrix(this.SizeY, otherMatrix.SizeX);
                //On multiplie chaque ligne par chacune des colonnes et on additionne les produits
                for (int ctrLine = 0; ctrLine < this.SizeY; ctrLine++)
                {
                    for (int ctrCol2 = 0; ctrCol2 < otherMatrix.SizeX; ctrCol2++)
                    {
                        total = 0;
                        for (int ctrCol = 0; ctrCol < this.SizeX; ctrCol++)
                        {
                            total += this[ctrLine, ctrCol] * otherMatrix[ctrCol, ctrCol2];
                        }
                        productMatrix[ctrLine, ctrCol2] = total;
                    }
                }
            }
            else
                throw new IncompatibleMatrixForProductException("Les matrices ne sont pas compatible pour une multiplication");

            return productMatrix;
        }

        public static Matrix operator *(Matrix m1, Matrix m2)
        {
            return m1.MatrixProduct(m2);
        }

        #endregion

        /**
         * Méthode qui découpe une partie de la matrice et en crée une plus petite
         **/
        public Matrix SubMatrix(int lineToIgnore, int columnToIgnore)
        {
            int newSizeY = (lineToIgnore >= 0) ? this.SizeY - 1 : this.SizeY;
            int newSizeX = (columnToIgnore >= 0) ? this.SizeX - 1 : this.SizeX;

            Matrix subMatrix = new Matrix(newSizeY, newSizeX);

            int ctrLineSM = -1;
            int ctrColSM;

            for (int ctrLine = 0; ctrLine < this.SizeY; ctrLine++)
            {
                if (ctrLine != lineToIgnore)
                {
                    ctrLineSM++;
                    ctrColSM = -1;
                    for (int ctrCol = 0; ctrCol < this.SizeX; ctrCol++)
                    {
                        if (ctrCol != columnToIgnore)
                        {
                            ctrColSM++;
                            subMatrix[ctrLineSM, ctrColSM] = this[ctrLine, ctrCol];
                        }
                    }
                }
            }

            return subMatrix;
        }

        /**
         * Crée une autre matrice identique
         **/
        public Matrix DeepCopy()
        {
            Matrix matrixClone = new Matrix(SizeY, SizeX);
            for (int ctrY = 0; ctrY < SizeY; ctrY++)
                for (int ctrX = 0; ctrX < SizeX; ctrX++)
                    matrixClone[ctrY, ctrX] = this[ctrY, ctrX];
            return matrixClone;
        }

        public double Determinant()
        {
            double determinant = 0;
            Matrix matrixCalcul;

            if (IsSquare)
            {
                if (this.SizeX == 1)
                {
                    determinant = this[0, 0];
                }
                else if (this.SizeX == 2)
                {
                    determinant = this[0, 0] * this[1, 1] - this[0, 1] * this[1, 0];
                }
                else
                {
                    matrixCalcul = new Matrix(this.SizeY - 1, this.SizeX - 1);
                    bool inverseSign = false;

                    for (int ctrCol = 0; ctrCol < this.SizeX; ctrCol++)
                    {
                        if (inverseSign)
                            determinant += this[0, ctrCol] * (this.SubMatrix(0, ctrCol).Determinant() * -1);
                        else
                            determinant += this[0, ctrCol] * this.SubMatrix(0, ctrCol).Determinant();

                        inverseSign = !inverseSign;
                    }
                }

            }

            return determinant;
        }

        public bool DimensionEquals(Matrix otherMatrice)
        {
            return (this.SizeX == otherMatrice.SizeX && this.SizeY == otherMatrice.SizeY);
        }

        public override int GetHashCode()
        {
            bool isInit = false;
            int hashCode = 0;

            for (int ctrY = 0; ctrY < SizeY; ctrY++)
                for (int ctrX = 0; ctrX < SizeX; ctrX++)
                    if (isInit)
                        hashCode = hashCode ^ this[ctrY, ctrX].GetHashCode();
                    else
                    {
                        hashCode = this[ctrY, ctrX].GetHashCode();
                        isInit = true;
                    }

            return hashCode;
        }

        public override bool Equals(object obj)
        {
            Matrix otherMatrix = null;
            otherMatrix = (obj as Matrix);

            if (otherMatrix == null)
                return false;

            if (!this.DimensionEquals(otherMatrix))
                return false;

            for (int ctrY = 0; ctrY < SizeY; ctrY++)
                for (int ctrX = 0; ctrX < SizeX; ctrX++)
                    if (!this[ctrY, ctrX].Equals(otherMatrix[ctrY, ctrX]))
                        return false;

            return true;
        }

        public override string ToString()
        {
            string sortie = String.Empty;
            for (int ctrY = 0; ctrY < SizeY; ctrY++)
            {
                for (int ctrX = 0; ctrX < SizeX; ctrX++)
                {
                    if (ctrX == SizeX - 1)
                        sortie += Math.Round(this[ctrY, ctrX], 3) + "\r\n";
                    else
                        sortie += Math.Round(this[ctrY, ctrX], 3) + ",";
                }
            }

            return sortie;
        }

        #endregion
    }

    public enum TriangularType
    {
        SuperiorTriangular = 0,
        InferiorTriangular = 1,
        DualTriangular = 2,
    }
}
